//
// Created by gejingguo on 16-3-19.
//

#include "Redis.h"
#include "RedisError.h"

#include <hiredis/hiredis.h>
#include <cassert>
#include <strings.h>

Redis::Redis():m_context(NULL) {
}

Redis::~Redis() {
    if(m_context != NULL) {
        redisFree(m_context);
        m_context = NULL;
    }
}

ErrorPtr Redis::Init(const std::string &host, int port, int msec) {
    if(m_context != NULL) {
        redisFree(m_context);
        m_context = NULL;
    }

    struct timeval tv;
    tv.tv_sec = msec/1000;
    tv.tv_usec = (msec - tv.tv_sec*1000)*1000;
    assert(tv.tv_usec >= 0);
    m_context = redisConnectWithTimeout(host.c_str(), port, tv);
    if(m_context == NULL) {
        return Error::Create(RedisError_ContextNull, "redisConnect context null");
    }
    if(m_context->err > 0) {
        return Error::Create(RedisError_ConnectFailed, m_context->errstr);
    }

    return NULL;
}

ErrorPtr Redis::Get(const std::string &key, std::string &value) {
    if(m_context == NULL) {
        return Error::Create(RedisError_ContextNull, "redisConnect context null");
    }
    struct redisReply* replay = (struct redisReply*)redisCommand(m_context, "GET %s", key.c_str());
    if(replay == NULL) {
        return Error::Create(RedisError_ReplyNull, "redisCommand Get Replay Null");
    }
    ErrorPtr err = NULL;
    if(replay->type == REDIS_REPLY_NIL) {
        err = Error::Create(RedisError_GetResultNil, "redisCommand Get replay nil");
    } else if(replay->type == REDIS_REPLY_ERROR) {
        err = Error::Create(RedisError_GetResultErr, replay->str);
    } else if(replay->type != REDIS_REPLY_STRING) {
        char msg[64] = {0};
        snprintf(msg, sizeof(msg)-1, "replay type(%d) err", replay->type);
        err = Error::Create(RedisError_GetResultTypeErr, msg);
    }

    if(err) {
        freeReplyObject(replay);
        return err;
    }

    value = replay->str;
    freeReplyObject(replay);
    return NULL;
}

ErrorPtr Redis::Set(const std::string &key, const std::string &value) {
    if(m_context == NULL) {
        return Error::Create(RedisError_ContextNull, "redisConnect context null");
    }
    struct redisReply* replay = (struct redisReply*)redisCommand(m_context, "SET %b %b", key.c_str(), key.size(), value.c_str(), value.size());
    if(replay == NULL) {
        return Error::Create(RedisError_ReplyNull, "redisCommand set Replay Null");
    }
    if(replay->type != REDIS_REPLY_STATUS || (strncasecmp(replay->str, "OK", replay->len) != 0 && strncasecmp(replay->str, "QUEUED", replay->len) != 0)) {
        std::string msg = replay->str;
        freeReplyObject(replay);
        return Error::Create(RedisError_SetResultErr, msg);
    }

    freeReplyObject(replay);
    return NULL;
}

ErrorPtr Redis::Del(const std::string &key) {
    if(m_context == NULL) {
        return Error::Create(RedisError_ContextNull, "redisConnect context null");
    }
    struct redisReply* replay = (struct redisReply*)redisCommand(m_context, "DEL %b", key.c_str(), key.size());
    if(replay == NULL) {
        return Error::Create(RedisError_ReplyNull, "redisCommand del Replay Null");
    }
    if(replay->type == REDIS_REPLY_STATUS && strncasecmp(replay->str, "QUEUED", replay->len)==0) {
        freeReplyObject(replay);
        return NULL;
    }
    if(replay->type != REDIS_REPLY_INTEGER) {
        std::string msg = replay->str;
        freeReplyObject(replay);
        return Error::Create(RedisError_DelKeyErr, msg);
    }

    freeReplyObject(replay);
    return NULL;
}

ErrorPtr Redis::IsExists(const std::string &key, bool& result) {
    if(m_context == NULL) {
        return Error::Create(RedisError_ContextNull, "redisConnect context null");
    }
    struct redisReply* replay = (struct redisReply*)redisCommand(m_context, "EXISTS %b", key.c_str(), key.size());
    if(replay == NULL) {
        return Error::Create(RedisError_ReplyNull, "redisCommand exists Replay Null");
    }
    if(replay->type != REDIS_REPLY_INTEGER) {
        std::string msg = replay->str;
        freeReplyObject(replay);
        return Error::Create(RedisError_ExistsKeyErr, msg);
    }

    result = (replay->integer > 0);
    return NULL;
}

ErrorPtr Redis::Watch(const std::string &key) {
    if(m_context == NULL) {
        return Error::Create(RedisError_ContextNull, "redisConnect context null");
    }
    struct redisReply* replay = (struct redisReply*)redisCommand(m_context, "WATCH %b", key.c_str(), key.size());
    if(replay == NULL) {
        return Error::Create(RedisError_ReplyNull, "redisCommand watch Replay Null");
    }
    if(replay->type == REDIS_REPLY_ERROR) {
        std::string msg = replay->str;
        freeReplyObject(replay);
        return Error::Create(RedisError_WatchKeyErr, msg);
    }

    freeReplyObject(replay);
    return NULL;
}

ErrorPtr Redis::Multi() {
    if(m_context == NULL) {
        return Error::Create(RedisError_ContextNull, "redisConnect context null");
    }
    struct redisReply* replay = (struct redisReply*)redisCommand(m_context, "MULTI");
    if(replay == NULL) {
        return Error::Create(RedisError_ReplyNull, "redisCommand multi Replay Null");
    }
    if(replay->type == REDIS_REPLY_ERROR) {
        std::string msg = replay->str;
        freeReplyObject(replay);
        return Error::Create(RedisError_MultiErr, msg);
    }

    freeReplyObject(replay);
    return NULL;
}

ErrorPtr Redis::Exec() {
    if(m_context == NULL) {
        return Error::Create(RedisError_ContextNull, "redisConnect context null");
    }
    struct redisReply* replay = (struct redisReply*)redisCommand(m_context, "EXEC");
    if(replay == NULL) {
        return Error::Create(RedisError_ReplyNull, "redisCommand exec Replay Null");
    }
    if(replay->type == REDIS_REPLY_ERROR) {
        std::string msg = replay->str;
        freeReplyObject(replay);
        return Error::Create(RedisError_ExecErr, msg);
    } else if(replay->type == REDIS_REPLY_NIL) {
        return Error::Create(RedisError_ExecInter, "redisCommand exec abort");
    }

    freeReplyObject(replay);
    return NULL;
}